/*
 * swTWI.cpp
 *
 *  Created on: 01-09-2014
 *      Author: Roper123pl
 *
 *  based on: Arduino Wire library
 */

#include <Arduino.h>
#include "swTWI.h"



#define twicbi(reg, bit) *reg &= ~bit		// macro to clear bit
#define twisbi(reg, bit) *reg |= bit		// macro to set bit
#define twild(reg, bit) (*reg & bit)		// macro to read bit



/*
 * Constructor
 */
swTWI::swTWI() {
	_bufferIndex = 0;
	_bufferLength = 0;

	_devAddr = 0x00;
	_transmitting = 0;
	_twiDelay = swTWI_SPEED_NORMAL;
}

/*
 * Public Methods
 */

/*
 * Function checks pins for TWI bus and initiates pin modes and levels.
 * Allocates memory for read and write buffers.
 *
 * @param sdaPin - Arduino pin for SDA signal
 * @param sclPin - Arduino pin for SCL signal
 *
 * @return TRUE if success
 *
 * @exam: for Arduino Pro Mini
 *   sdaPin - 7
 *   sclPin - 8
 */
uint8_t swTWI::begin(uint8_t sdaPin, uint8_t sclPin) {
	// Configuring SDA pin
	_sdaBit = digitalPinToBitMask(sdaPin);
	_sdaPort = digitalPinToPort(sdaPin);
	if (_sdaPort == NOT_A_PIN)
		return 0;
	_sdaMode = portModeRegister(_sdaPort);
	_sdaOut = portOutputRegister(_sdaPort);
	_sdaIn = portInputRegister(_sdaPort);
	twicbi(_sdaMode, _sdaBit);					// release SDA
	twicbi(_sdaOut, _sdaBit);					// set SDA pin as output

	// Configuring SCL pin
	_sclBit = digitalPinToBitMask(sclPin);
	_sclPort = digitalPinToPort(sclPin);
	if (_sclPort == NOT_A_PIN)
		return 0;
	_sclMode = portModeRegister(_sclPort);
	_sclOut = portOutputRegister(_sclPort);
	_sclIn = portInputRegister(_sclPort);
	twicbi(_sclMode, _sclBit);					// release SCL
	twicbi(_sclOut, _sclBit);					// set SCL pin as output

	// init buffer
	_buffer = (uint8_t*) calloc(BUFFER_LENGTH, sizeof(uint8_t));
	_bufferIndex = 0;
	_bufferLength = 0;

	_devAddr = 0x00;
	_transmitting = 0;

	return 1;
}

/*
 * Function checks pins for TWI bus and initiates pin modes and levels.
 * Allocates memory for read and write buffers.
 *
 * @param sdaPin - Arduino pin for SDA signal
 * @param sclPin - Arduino pin for SCL signal
 * @param speed - speed bus definition swTWI_SPEED_FAST/NORMAL/MEDIUM/SLOW
 *
 * @return TRUE if success
 *
 * @exam: for Arduino Pro Mini
 *   sdaPin - 7
 *   sclPin - 8
 */
uint8_t swTWI::begin(uint8_t sdaPin, uint8_t sclPin, uint8_t speed) {
	_twiDelay = speed;
	return begin(sdaPin, sclPin);
}

/*
 * Function sets speed of software TWI bus
 *
 * @param speed - three speed are allowed
 *   swTWI_SPEED_HIGH 	- clock ~100kHz
 *   swTWI_SPEED_NORMAL - clock ~75kHz (default)
 *   swTWI_SPEED_MEDIUM - clock ~50kHz
 *   swTWI_SPEED_SLOW	- clock ~25kHz
 *
 */
void swTWI::setSpeed(uint8_t speed) {
	if (!_transmitting) {
		_twiDelay = speed;
	}
}

/*
 * Function returns quantity of bytes available in buffer.
 * If no data available buffer is reset additionally.
 *
 * @return bytes available in buffer
 *
 * @usage:
 *   - after requestFrom(address, numBytes)
 */
int swTWI::available(void) {
	uint8_t result = 0;

	result = _bufferLength - _bufferIndex;
	if (!result) {
		_bufferIndex = 0;						// all data read - reset buffer
		_bufferLength = 0;
	}

	return result;
}

/*
 * Function read and return one byte from buffer
 * and move buffer pointer to next position
 *
 * @usage:
 *   after requestFrom(address, numBytes)
 */
int swTWI::read(void) {
	int value = -1;

	if ( _bufferIndex < _bufferLength ) {
		value = _buffer[_bufferIndex];			// get byte from buffer
		++_bufferIndex;							// point to next byte
	} else {
		_bufferIndex = 0;						// all data read - reset buffer
		_bufferLength = 0;
	}
	return value;
}

/*
 * Function writes byte into buffer - prepares them for writing to bus
 *
 * @param data to write
 *
 * @return number of bytes writes into buffer
 *
 * @usage:
 *   after beginTransmission(address)
 */
uint8_t swTWI::write(uint8_t data) {
	if (_transmitting) {
		if (_bufferLength >= BUFFER_LENGTH) {	// checking if buffer is full
			return 0;
		}
		_buffer[_bufferIndex] = data;			// store  in buffer
		++_bufferIndex;							// increment buffer pointer
		_bufferLength = _bufferIndex;		// update buffer length
	}

	return 1;
}

/*
 * Function writes 'quantity' of bytes into buffer - prepares them
 * for writing to bus
 *
 * @param pointer to data array
 * @param quantity of byte to write
 *
 * @return number of byte written into buffer
 *
 * @usage:
 *   after beginTransmission(address)
 */
uint8_t swTWI::write(const uint8_t *data, size_t quantity) {
	uint8_t i;

	if (_transmitting) {
		if ((_bufferLength + quantity) >= BUFFER_LENGTH) {	// checking if buffer is full
			return 0;
		}
		for (i = 0; i < quantity; ++i) {
			write(data[i]);
		}
		return quantity;
	} else {
		return 0;
	}
}

/*
 * Function prepares for transmitting to slave device
 *
 * @param address of slave device
 */
void swTWI::beginTransmission(uint8_t address) {
	_transmitting = 1;							// set transmitting flag
	_devAddr = address;							// store slave address
	_bufferIndex = 0;							// reset buffer
	_bufferLength = 0;
}

/*
 * Function prepares for transmitting to slave device
 *
 * @param address of slave device
 */
void swTWI::beginTransmission(int address) {
  beginTransmission((uint8_t)address);
}

/*
 * Function completes transmission to TWI bus.
 * In the fact this function makes whole transmission in real:
 * 	- issues START condition
 * 	- sends slave device address
 * 	- sends data to slave device
 * 	- issues STOP condition
 *
 * 	@param if TRUE STOP condition will be send at the end
 *
 * 	@return number of sent bytes
 */
uint8_t swTWI::endTransmission(uint8_t sendStop) {
	uint8_t result = 0;

	_sendStart();								// issue START condition
	result = _write((_devAddr << 1) | swTWI_WRITE);// send slave address
	if (!result) {								// check if slave sent ACK
		return 0;								// NACK
	}
	result = _write(_buffer, _bufferLength);
	if (sendStop) {
		_sendStop();							// issue STOP condition
	}
	_bufferIndex = 0;							// clears buffer
	_bufferLength = 0;
	_transmitting = 0;							// clears transmitting flag

	return result;
}

/*
 * Function completes transmission to TWI bus.
 * In the fact this function makes whole transmission in real:
 * 	- issues START condition
 * 	- sends slave device address
 * 	- sends data to slave device
 * 	- issues STOP condition
 *
 * 	@param if TRUE STOP condition will be send at the end
 *
 * 	@return number of sent bytes
 */
uint8_t swTWI::endTransmission(void) {
	return endTransmission(true);
}

/*
 * Function read 'quantity' of byte into buffer from slave device
 *
 * @param slave device address
 * @param quantity of byte to read
 * @param TRUE if send stop at the end
 *
 * @return number of received bytes
 */
uint8_t swTWI::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) {
	uint8_t result = 0;

	if (quantity > BUFFER_LENGTH) {				// check amount of data
		quantity = BUFFER_LENGTH;				// limit buffer length
	}

	_bufferIndex = 0;							// clears buffer
	_bufferLength = 0;
	_transmitting = 0;							// clears transmitting flag

	_sendStart();								// issue START condition
	result = _write((address << 1) | swTWI_READ);// send slave address
	if (!result) {								// check if slave sent ACK
		return 0;								// NACK
	}
	result = _read(_buffer, quantity);			// perform read
	_bufferLength = result;						// set buffer length
	if (sendStop) {
		_sendStop();							// issue STOP condition
	}

	return result;
}

/*
 * Function read 'quantity' of byte into buffer from slave device
 *
 * @param slave device address
 * @param quantity of byte to read
 * @param TRUE if send stop at the end
 *
 * @return number of received bytes
 */
uint8_t swTWI::requestFrom(uint8_t address, uint8_t quantity) {
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

/*
 * Function read 'quantity' of byte into buffer from slave device
 *
 * @param slave device address
 * @param quantity of byte to read
 * @param TRUE if send stop at the end
 *
 * @return number of received bytes
 */
uint8_t swTWI::requestFrom(int address, int quantity) {
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

/*
 * Function read 'quantity' of byte into buffer from slave device
 *
 * @param slave device address
 * @param quantity of byte to read
 * @param TRUE if send stop at the end
 *
 * @return number of received bytes
 */
uint8_t swTWI::requestFrom(int address, int quantity, int sendStop) {
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop);
}


//----------------------
// Low level function
//----------------------

/*
 * Issue a start condition.
 */
void swTWI::_sendStart(void) {
	delayMicroseconds(_twiDelay);
	twisbi(_sdaMode, _sdaBit);					// force SDA low
	delayMicroseconds(_twiDelay);
	twisbi(_sclMode, _sclBit);					// force SCL low
	delayMicroseconds(_twiDelay);
}

/*
 * Issue a stop condition.
 */
void swTWI::_sendStop(void) {
	twisbi(_sclMode, _sclBit);					// force SCL low
	twisbi(_sdaMode, _sdaBit);					// force SDA low
	delayMicroseconds(_twiDelay);
	twicbi(_sclMode, _sclBit);					// release SCL
	delayMicroseconds(_twiDelay);
	twicbi(_sdaMode, _sdaBit);					// release SDA
	delayMicroseconds(_twiDelay);
}

/*
 * Issue a restart condition.
 */
void swTWI::_sendRestart(void) {
	delayMicroseconds(_twiDelay);
	twisbi(_sclMode, _sclBit);					// force SCL low
	twisbi(_sdaMode, _sdaBit);					// force SDA low
	delayMicroseconds(_twiDelay);
	twicbi(_sdaMode, _sdaBit);					// release SDA
	twicbi(_sclMode, _sclBit);					// release SCL
	delayMicroseconds(_twiDelay);
	_sendStart();
}

/*
 * Reads byte from TWI bus.
 *
 * @param sendAck - if TRUE send ACK bit
 *
 * @return received byte
 */
uint8_t swTWI::_read(uint8_t sendAck) {
	uint8_t value = 0;

	twisbi(_sclMode, _sclBit);				// force SCL low
	twicbi(_sdaMode, _sdaBit);				// release SDA
	delayMicroseconds(_twiDelay);
	for (uint8_t i = 0; i < 8; i++) {
		value <<= 1;						// shift left by one bit
		twicbi(_sclMode, _sclBit);			// release SCL
		while (!twild(_sclIn, _sclBit));	// loop until SCL is high (allow slave to stretch SCL)
		delayMicroseconds(_twiDelay);
		value |= twild(_sdaIn, _sdaBit) ? 0x01 : 0x00;	// get SDA value
		twisbi(_sclMode, _sclBit);			// force SCL low
		delayMicroseconds(_twiDelay);
	}

	// send ACK/NACK bit
	if (sendAck) {
		twisbi(_sdaMode, _sdaBit);			// force SDA low
	} else {
		twicbi(_sdaMode, _sdaBit);			// release SDA
	}
	//delayMicroseconds(_twiDelay);
	twicbi(_sclMode, _sclBit);				// release SCL
	while (!twild(_sclIn, _sclBit));		// wait SCL high
	delayMicroseconds(_twiDelay);
	twisbi(_sclMode, _sclBit);				// force SCL low
	twisbi(_sdaMode, _sdaBit);				// force SDA low

	return value;
}

/*
 * Reads byte from TWI bus with ACK
 *
 *
 * @return received byte
 */
uint8_t swTWI::_read(void) {
	return _read(true);
}

/*
 * Function reads few bytes from specified device into buffer
 *
 * @param *buffer - buffer to read to
 * @param quantity - quantity of bytes to read
 *
 * @return quantity read bytes
 */
uint8_t swTWI::_read(uint8_t* buffer, uint8_t quantity) {
	uint8_t i;

	for (i = 0; i < quantity; i++) {
		buffer[i] = _read(i < (quantity - 1));	// read until last byte
												// last byte with out ACK
	}

	return i;
}

/*
 * Writes byte to TWI bus.
 *
 * @param data to sent to the TWI bus
 *
 * @return ACK/NACK bit - TRUE for acknowledge
 */
uint8_t swTWI::_write(uint8_t data) {
	uint8_t result = 0;
	uint8_t mask;

	twisbi(_sclMode, _sclBit);					// force SCL low
	twisbi(_sdaMode, _sdaBit);					// force SDA low
	for (mask = 0X80; mask != 0; mask >>= 1) {
		if (data & mask) {						// mask current bit to send
			twicbi(_sdaMode, _sdaBit);			// release SDA - transmit '1'
		} else {
			twisbi(_sdaMode, _sdaBit);			// force SDA low - transmit '0'
		}
		delayMicroseconds(_twiDelay);
		twicbi(_sclMode, _sclBit);				// release SCL
		delayMicroseconds(_twiDelay);
		twisbi(_sclMode, _sclBit);				// force SCL low
	}

	// SCL pulse for ACK bit
	twicbi(_sdaMode, _sdaBit);					// release SDA
	delayMicroseconds(_twiDelay);
	twicbi(_sclMode, _sclBit);					// release SCL
	while(!twild(_sclIn, _sclBit));				// wait SCL high (in case wait states are inserted)
	result = twild(_sdaIn, _sdaBit);			// get SDA state - ACK/NACK bit
	delayMicroseconds(_twiDelay);
	twisbi(_sclMode, _sclBit);					// force SCL low
	twisbi(_sdaMode, _sdaBit);					// force SDA low

	return (result == 0);
}

/*
 * Function writes few bytes to specified device from buffer
 *
 * @param *buffer - buffer to write from
 * @param quantity - quantity of bytes to write
 *
 * @return quantity written bytes
 */
uint8_t swTWI::_write(uint8_t* buffer, uint8_t quantity) {
	uint8_t i;

	for (i = 0; i < quantity; i++) {
		if (!_write(buffer[i])) {					// write byte to bus
			break;									// if NACK stop sending
		}
	}

	return i;
}



/*
 *  Preinstantiate Objects
 */
swTWI swWire = swTWI();

